#include "records.h"
#include "logger.hpp"

extern loglevel_enum loglevel;
string Records::latestCategorySearch = "DEFAULT";

//@author A0096566M
int Records::getRecordSize() {
	return recordList.size();
}
//@author A0096566M
int Records::addData(Task task){
	recordList.push_back(task);
	updateFile();
	return 0;
}
//@author A0096566M
Task Records::getData(int n){
	if (n < 0 || n >= recordList.size()) throw "invalid getData";
	assert(n>=0);
	assert(n<recordList.size());
	return recordList.at(n);
}
//@author A0096566M
int Records::delData(int n){
	recordList.erase(recordList.begin()+n);
	updateFile();
	return 0;
}
//@author A0096566M
int Records::setData(int n, Task task) {
	recordList[n] = task;
	updateFile();
	return 0;
}

//@author A0105541U
int Records::append(Records list) {
	vector<Task> tasks = list.getTasks();
	for(std::vector<Task>::iterator itr = tasks.begin(); itr != tasks.end(); itr++) {
		if (this->getIndexByTaskId(itr->getId()) == -1) {
			this->addData(*itr);
		}
	}
	return 0;
}
//@author A0096566M
bool Records::empty(){
	return recordList.empty();
}
//@author A0096566M
vector<Task> Records::getTasks(){
	return recordList;
}
//@author A0096566M
void Records::setTasks(vector<Task> newRecordList){
	recordList = newRecordList;
}
//@author A0096566M
int Records::updateFile(){//Updates File to become records held now.
	if(fileInt)
		return fileInt->writeFile();
	else
		return -1;
}
//@author A0096566M
int Records::getIndex(Task task) {
	return getIndexByTaskId(task.getId());
}

//@author A0105541U
int Records::getIndexByTaskId(int id) {
	for (int i = 0; i < this->getRecordSize(); i ++ ) {
		if (recordList.at(i).getId() == id) {
			return i;
		}
	}
	return -1;
}
//@author A0096566M
int Records::initialize(FileInterface* fInt){
	setFileInterface(fInt);
	fInt->setRecords(*this);
	return getFromFile();
	//return 0;
}
//@author A0096566M
int Records::delDataAll() {
	recordList.clear();
	updateFile();
	return 0;
}
//@author A0096566M
int Records::setFileInterface(FileInterface* fInt){
	fileInt = fInt;
	return 0;
}
//@author A0096566M
FileInterface* Records::getFileInterface(){
	return this->fileInt;
}
//@author A0096566M
int Records::getFromFile(){//Updates all content to become file content
	return fileInt->readFile();
}

//@author A0101728M
//Filter methods, return new Records
Records Records::filterByTaskNameContains(bool in, string target){
	Records rec(*this);
	for (std::vector<Task>::iterator itr = rec.recordList.begin(); itr != rec.recordList.end();) {
		if (in){
			if (itr->nameContainsSubsequence(target)){
				itr++;
			}
			else {
				itr = rec.recordList.erase(itr);
			}
		}
		else {
			if (itr->nameContainsSubsequence(target)){
				itr = rec.recordList.erase(itr);
			}
			else {
				itr++;
			}
		}
	}
	//sort according to length of task name
	//all results contain the search string, the shorter the task name, the closer the match
	if (in){
		vector<Task> recordTasks = rec.getTasks();
		std::sort(recordTasks.begin(), recordTasks.end(), isNameShorter);
		rec.setTasks(recordTasks);
	}
	return rec;
}

Records Records::filterByCategory(bool in, string category){
	Records rec(*this);
	for (std::vector<Task>::iterator itr = rec.recordList.begin(); itr != rec.recordList.end();) {
		if (in){
			if (itr->categoriesContainSubsequence(category)){
				itr++;
			}
			else {
				itr = rec.recordList.erase(itr);
			}
		}
		else {
			if (itr->categoriesContainSubsequence(category)){
				itr = rec.recordList.erase(itr);
			}
			else {
				itr++;
			}
		}
	}
	//if filtering in, 
	//sort according to number of categories that contain the search string, then by length of category name
	//else, stable.
	//all results contain the search string, the shorter the category name, the closer the match
	if (in){
		vector<Task> recordTasks = rec.getTasks();
		std::sort(recordTasks.begin(), recordTasks.end(), isBetterCategoryMatch);
		rec.setTasks(recordTasks);
	}
	return rec;
}

Records Records::filterByDone(bool in, bool done){
	Records rec(*this);
	for (std::vector<Task>::iterator itr = rec.recordList.begin(); itr != rec.recordList.end();) {
		if (in){
			if (itr->isDone() == done){
				itr++;
			}
			else {
				itr = rec.recordList.erase(itr);
			}
		}
		else {
			if (itr->isDone() == done){
				itr = rec.recordList.erase(itr);
			}
			else {
				itr++;
			}
		}
	}
	return rec;
}

Records Records::filterByUpcoming(bool in, bool upcoming){
	Records rec(*this);
	for (std::vector<Task>::iterator itr = rec.recordList.begin(); itr != rec.recordList.end();) {
		if (in){
			if (itr->isUpcoming() == upcoming){
				itr++;
			}
			else {
				itr = rec.recordList.erase(itr);
			}
		}
		else {
			if (itr->isUpcoming() == upcoming){
				itr = rec.recordList.erase(itr);
			}
			else {
				itr++;
			}
		}
	}
	return rec;
}

Records Records::filterByDateBefore(bool in, Date date){
	Records rec(*this);
	for (std::vector<Task>::iterator itr = rec.recordList.begin(); itr != rec.recordList.end();) {
		if (in){
			if (itr->isBefore(date)){
				itr++;
			}
			else {
				itr = rec.recordList.erase(itr);
			}
		}
		else {
			if (itr->isBefore(date)){
				itr = rec.recordList.erase(itr);
			}
			else {
				itr++;
			}
		}
	}
	return rec;
}

Records Records::filterByDateAfter(bool in, Date date){
	Records rec(*this);
	for (std::vector<Task>::iterator itr = rec.recordList.begin(); itr != rec.recordList.end();) {
		if (in){
			if (itr->isAfter(date)){
				itr++;
			}
			else {
				itr = rec.recordList.erase(itr);
			}
		}
		else {
			if (itr->isAfter(date)){
				itr = rec.recordList.erase(itr);
			}
			else {
				itr++;
			}
		}
	}
	return rec;
}

Records Records::filterByDateBetween(bool in, Date dateStart, Date dateEnd){
	Records rec(*this);
	for (std::vector<Task>::iterator itr = rec.recordList.begin(); itr != rec.recordList.end();) {
		if (in){
			if (itr->isBetween(dateStart, dateEnd)){
				itr++;
			}
			else {
				itr = rec.recordList.erase(itr);
			}
		}
		else {
			if (itr->isBetween(dateStart, dateEnd)){
				itr = rec.recordList.erase(itr);
			}
			else {
				itr++;
			}
		}
	}
	return rec;
}

Records Records::filterBySameDay(bool in, Date date){
	Records rec(*this);
	int year = date.getYear();
	int month = date.getMonth();
	int day = date.getDate();
	Date startDate = Date(year, month + 1, day, 0, 0);
	Date endDate = startDate;
	startDate.addMinutes(-1);
	endDate.addDays(1);
	return filterByDateBetween(in, startDate, endDate);
}

Records Records::filterByOverdue(bool in, bool overdue){
	Records rec(*this);
	for (std::vector<Task>::iterator itr = rec.recordList.begin(); itr != rec.recordList.end();) {
		if (in){
			if (itr->isOverdue() == overdue){
				itr++;
			}
			else {
				itr = rec.recordList.erase(itr);
			}
		}
		else {
			if (itr->isOverdue() == overdue){
				itr = rec.recordList.erase(itr);
			}
			else {
				itr++;
			}
		}
	}
	return rec;
}

Records Records::filterByTaskType(bool in, TaskType type){
	Records rec(*this);
	for (std::vector<Task>::iterator itr = rec.recordList.begin(); itr != rec.recordList.end();) {
		if (in){
			if (itr->getTaskType() == type){
				itr++;
			}
			else {
				itr = rec.recordList.erase(itr);
			}
		}
		else {
			if (itr->getTaskType() == type){
				itr = rec.recordList.erase(itr);
			}
			else {
				itr++;
			}
		}
	}
	return rec;
}

Records Records::filterByFuzzyContains(bool in, string target, int threshold){
	log(LOG_DEBUG) << "Records::filterByFuzzyContains - " << "Filtering for names that fuzzily match " << target << " with threshold: " << threshold << "%";
	Records rec(*this);
	for (std::vector<Task>::iterator itr = rec.recordList.begin(); itr != rec.recordList.end();) {
		if (in){
			if (itr->nameContainsFuzzy(target, threshold)){
				itr++;
			}
			else {
				itr = rec.recordList.erase(itr);
			}
		}
		else {
			if (itr->nameContainsFuzzy(target, threshold)){
				itr = rec.recordList.erase(itr);
			}
			else {
				itr++;
			}
		}
	}
	return rec;
}

//Private helper methods, comparators for sorts.
bool Records::isStartEarlier(Task a, Task b){
	TaskType aTaskType = a.getTaskType();
	TaskType bTaskType = b.getTaskType();
	if (aTaskType == FLOATING && bTaskType == FLOATING){
		return false;
	}
	else if (bTaskType == FLOATING){
		return true;
	}
	else if (aTaskType == FLOATING){
		return false;
	}
	else if (aTaskType == SCHEDULE && bTaskType == SCHEDULE){
		return a.getStart() < b.getStart();
	}
	else if (aTaskType == SCHEDULE && bTaskType == DEADLINE){
		return true;
	}
	else if (aTaskType == DEADLINE && bTaskType == SCHEDULE){
		return false;
	}
	else{
		//both: Deadline
		return a.getEnd() < b.getEnd();
	}
}

bool Records::isStartLater(Task a, Task b){
	TaskType aTaskType = a.getTaskType();
	TaskType bTaskType = b.getTaskType();
	if (aTaskType == FLOATING && bTaskType == FLOATING){
		return false;
	}
	else if (aTaskType == FLOATING){
		return true;
	}
	else if (bTaskType == FLOATING){
		return false;
	}
	else if (aTaskType == SCHEDULE && bTaskType == SCHEDULE){
		return a.getStart() < b.getStart();
	}
	else if (aTaskType == SCHEDULE && bTaskType == DEADLINE){
		return true;
	}
	else if (aTaskType == DEADLINE && bTaskType == SCHEDULE){
		return false;
	}
	else{
		//both: Deadline
		return a.getEnd() < b.getEnd();
	}
}

bool Records::isEndEarlier(Task a, Task b){
	if (a.getTaskType() == FLOATING && b.getTaskType() == FLOATING){
		return false;
	}
	else if (b.getTaskType() == FLOATING){
		return true;
	}
	else if (a.getTaskType() == FLOATING){
		return false;
	}
	else{
		return a.getEnd() < b.getEnd();
	}
}

bool Records::isEndLater(Task a, Task b){
	if (a.getTaskType() == FLOATING && b.getTaskType() == FLOATING){
		return false;
	}
	else if (b.getTaskType() == FLOATING){
		return false;
	}
	else if (a.getTaskType() == FLOATING){
		return true;
	}
	else{
		return a.getEnd() > b.getEnd();
	}
}

bool Records::isNameShorter(Task a, Task b){
	return a.getTaskName().length() < b.getTaskName().length();
}

bool Records::isNameLonger(Task a, Task b){
	return a.getTaskName().length() > b.getTaskName().length();
}

bool Records::isAlphaEarlier(Task a, Task b){
	return boost::algorithm::to_lower_copy(a.getTaskName()) 
		< boost::algorithm::to_lower_copy(b.getTaskName());
}

bool Records::isAlphaLater(Task a, Task b){
	return boost::algorithm::to_lower_copy(a.getTaskName())
		> boost::algorithm::to_lower_copy(b.getTaskName());
}

bool Records::isBetterCategoryMatch(Task a, Task b){
	//count number of categories that match
	vector<string> aCategories = a.getCategories();
	int aCount = 0;
	int aMatchLengths = 0;
	for (std::vector<string>::iterator itr = aCategories.begin(); itr != aCategories.end();) {
		if (!Task::categoryContainsSubsequence(*itr, latestCategorySearch)){
			aCount++;
			string category = *itr;
			aMatchLengths += category.length() * category.length();
			itr = aCategories.erase(itr);
		}
		else{
			itr++;
		}
	}

	vector<string> bCategories = b.getCategories();
	int bCount = 0;
	int bMatchLengths = 0;
	for (std::vector<string>::iterator itr = bCategories.begin(); itr != bCategories.end();) {
		if (!Task::categoryContainsSubsequence(*itr, latestCategorySearch)){
			bCount++;
			string category = *itr;
			bMatchLengths += category.length() * category.length();
			itr = bCategories.erase(itr);
		}
		else{
			itr++;
		}
	}

	//if same, sort by sum of squares of matching category lengths
	if (aCount == bCount){
		return aMatchLengths < bMatchLengths;
	}
	else{
		return aCount < bCount;
	}
}

//Sort methods, return new Records
Records Records::sortByDateEnd(bool asc){
	Records result(*this);
	if (asc){
		sort(result.recordList.begin(), result.recordList.end(), Records::isEndEarlier);
	}
	else {
		sort(result.recordList.begin(), result.recordList.end(), Records::isEndLater);
	}
	return result;
}

Records Records::sortByDateStart(bool asc){
	Records result(*this);
	if (asc){
		sort(result.recordList.begin(), result.recordList.end(), Records::isStartEarlier);
	}
	else {
		sort(result.recordList.begin(), result.recordList.end(), Records::isStartLater);;
	}
	return result;
}

Records Records::sortByNameAlphabetical(bool asc){
	Records result(*this);
	if (asc){
		std::sort(result.recordList.begin(), result.recordList.end(), Records::isAlphaEarlier);
	}
	else {
		std::sort(result.recordList.begin(), result.recordList.end(), Records::isAlphaLater);
	}
	return result;
}

Records Records::sortByNameLength(bool asc){
	Records result(*this);
	if (asc){
		std::sort(result.recordList.begin(), result.recordList.end(), Records::isNameShorter);
	}
	else {
		std::sort(result.recordList.begin(), result.recordList.end(), Records::isNameLonger);
	}
	return result;
}

Records Records::sortByDone(bool asc){
	Records result(*this);
	Records firstChunk = result.filterByDone(true, asc);
	Records secondChunk = result.filterByDone(true, !asc);
	sort(firstChunk.recordList.begin(), firstChunk.recordList.end(), Records::isEndEarlier);
	sort(secondChunk.recordList.begin(), secondChunk.recordList.end(), Records::isEndEarlier);
	result.delDataAll();
	result.append(firstChunk);
	result.append(secondChunk);
	return result;
}

Records Records::sortByOverdue(bool asc){
	Records result(*this);
	Records firstChunk = result.filterByOverdue(true, asc);
	Records secondChunk = result.filterByOverdue(true, !asc);
	sort(firstChunk.recordList.begin(), firstChunk.recordList.end(), Records::isEndEarlier);
	sort(secondChunk.recordList.begin(), secondChunk.recordList.end(), Records::isEndEarlier);
	result.delDataAll();
	result.append(firstChunk);
	result.append(secondChunk);
	return result;
}

//@author ?
string Records::toString(){

	/*
	string s = "<div style=\"margin: 2px\">";
	for(unsigned int i=0 ; i<recordList.size() ; i++){
		s += "<span style=\"display: inline-block; width:5% ;\">";
		s += std::to_string((long double)i+1);
		s += ". </span> ";
        s += recordList[i].toFileString();
		cout << s << endl;

	}
	return s+"</div>";
	*/
	/*
	string s = "";
	for(unsigned int i=0 ; i<recordList.size() ; i++){
		s += std::to_string((long double)i+1);
		s += ". </span> ";
        s += recordList[i].toFileString();
		s += "\n";
	}
	return (s+"\n");
	*/
	//The old display.

	string s="<table cellpadding=\"3\">";
	for(unsigned int i=0 ; i<recordList.size() ; i++){
		s += "<tr><td>";
		s += std::to_string((long double)i+1);
		s += "</td>";
        s += recordList[i].toTabledHTMLString();
		s += "</tr>";
	}
	return s+"</table>";
	
	

	/*
	string s = "";
	for(unsigned int i=0 ; i<recordList.size() ; i++){
		s += std::to_string((long double)i+1);
		s += ". ";
        s += recordList[i].toHTMLString();
        s += "<br>\n";
	}
	return s;
	*/
}

string Records::toNormalString(){
	string s;
	for(unsigned int i=0 ; i<recordList.size() ; i++){
		s += std::to_string((long double)i+1);
		s += ". ";
        s += recordList[i].toScreenString();
        s += "\n";
	}
	return s;
}
